#!/usr/bin/env perl
# Create the map of a processor grid

# For the Wrekagrid project, 
# November 2007, Basile Clout

use Getopt::Long;

use strict;
use warnings;


my $chkudp_bin = "./chkUDP.pl";
my $chkmpi_bin = "./chkMPI.pl";
my $wmapd_bin = "../daemons/hemapd.py";

my $chkudp_tcp_fl = "-p tcp";
my $chkudp_udp_fl = "-p udp";
my $chkmpi_fl = "";


sub usage(){

  print<<EOF;

  Usage: $0 [-f file containing the list of nodes] [-d debug] [-h
  help] [-user] [-l logfile] [--mpi map with mpi] [--udp] [--tcp]
  [--cpu] [-n integer n: number of bits exchanged per communication
  per node (DEFAULT=1)] [--pagrid] [--values] [--weights
  print the weights, not the absolute values] [--fast] <list of nodes>

  Basile Clout, November 2007

  Example: ./vmap.pl --udp --weights --values --fast -u basil -f lamhosts.txt
EOF
  exit;
}

my $file="../../data/hostfiles/lids.hosts";
my $help;
my $user = "root";
my @hosts;
my $ssh = "ssh";
my $logfile = "vmap.log";
my ($pagrid, $mpi, $udp, $tcp, $cpu, $weights, $fast, $values);
my $debug;
my $size = 5000;
my $m=1;

GetOptions("f=s"=>\$file, "h"=>\$help, "n=i"=>\$m, "d"=>\$debug, "u=s"=>\$user, "l=s"=>\$logfile, "ssh=s"=>\$ssh, "mpi"=>\$mpi, "udp"=>\$udp, "tcp"=>\$tcp, 'cpu'=>\$cpu, "pagrid"=>\$pagrid, "weights"=>\$weights, "fast"=>\$fast, "values"=>\$values);

usage() if ($help);

if ($#ARGV < 0 and !$file){
  print "Please enter at least one node, either on the command line or in a file\n";
  usage();
}

if ($#ARGV > -1){
  @hosts = sort @ARGV;
}else{
  open(HOSTS, "<$file") or die("$0: Impossible to open file $file: $!");
  my @oldhosts = <HOSTS>;
  @hosts = sort split /\s/, $oldhosts[$#oldhosts];
  close(HOSTS);
}


$pagrid = 1 if (!$mpi and !$udp and !$tcp and !$cpu and !$pagrid);

if ($fast){
  $chkudp_udp_fl .= " --fast";
  $chkudp_tcp_fl .= " --fast";
  $chkmpi_fl .= " --fast";
}

open LOG, ">$logfile" or die "Impossible to create logfile $logfile: $!";
my ($sec, $min, $hrs, $day, $month, $year) = (localtime) [0,1,2,3,4,5];
my $date = sprintf("%04d-%02d-%02d %d:%d:%d\n", $year+1900, $month+1, $day, $hrs, $min, $sec);
print LOG "#Basile Clout, $date \n\n";

my %hname;			# hash hostname => IP address
my %hrank;
my @aneig;
my @anet;
my @acpu;
my $list = join(' ', @hosts);  

foreach my $rank (0..$#hosts){
  $hrank{$hosts[$rank]}=$rank;
}

# Get neighbors, info and possibly cpu for each node

foreach my $node (0..$#hosts){

  my $order = "";
  if ($cpu or $pagrid){
    $order = "$ssh $hosts[$node] \"$wmapd_bin --net_off --set_pim2size $size --set_list \\\"$list\\\" --set_user $user\"";
  }else{
    $order = "$ssh $hosts[$node] \"$wmapd_bin --cpu_off --net_off --set_list \\\"$list\\\"\"";
  }
  print LOG "Node $hosts[$node]:\n";
  print LOG "Order = $order\n";

  open(NEIGH, "$order |") or die "Impossible to get neighbors for $node with order $order: $!";
  my @content = <NEIGH>;
  print LOG join('', @content);

  foreach my $line (@content){

    if ($line =~ /^neighbors/){
      my @parts = split /\s/, $line;
      shift @parts;
      $aneig[$node] = \@parts;
    }elsif ($line =~ /^node/){
      my @parts = split /\s/, $line;
      $hname{$parts[2]}=$parts[1];
    }elsif($line =~ /^cpu/){
      my @parts = split /\s/, $line;
      $acpu[$node]=$parts[1];
    }
  }
}

if ($debug){
  foreach my $node (0..$#hosts){
    print "neighs of $node = ", join(' & ', @{$aneig[$node]}), "\n";
    print "cpu of $node = $acpu[$node]\n" if ($cpu or $pagrid);
  }
}

# Get network characteristics

my $order = "";
if($mpi or $pagrid){
    $order = "$chkmpi_bin $chkmpi_fl -u $user $list";
  }elsif ($udp){
    $order = "$chkudp_bin $chkudp_udp_fl -c \"$list\"";
  }elsif($tcp){
    $order = "$chkudp_bin $chkudp_tcp_fl -c \"$list\"";
  }

print LOG "Order = $order\n";
  
open(NET, "$order |") or die "Impossible to get net with $order: $!";
my @content = <NET>;
print LOG join('', @content);

my $cnt=-1;
my @procs;
my $curnode="";
my %hash;
my $maxbdw = 0;
my $maxlat = 0;
my $minlat = 10000000000;
my $minbdw = 10000000000;

foreach my $line (@content){
  
  chomp $line;
  $cnt++;
  next if ($cnt==0);
  if ($cnt==1){
    @procs=split /\s+/, $line;
    shift @procs;
      next;
  }
  
  my @parts = split /\s+/, $line;
  
  if ($cnt =~ /[02468]$/){
    $curnode = shift @parts;
    foreach my $n (0..$#procs){
      $anet[int($hrank{$curnode})][$n][0]=$parts[$n];
      $maxbdw = $parts[$n] if ($parts[$n] !~ /-/ and $parts[$n] > $maxbdw);
      $minbdw = $parts[$n] if ($parts[$n] !~ /-/ and $parts[$n] < $minbdw);
    }
  }else{
    shift @parts;
    foreach my $n (0..$#procs){
      $anet[int($hrank{$curnode})][$n][1]=$parts[$n];
      $maxlat = $parts[$n] if ($parts[$n] !~ /-/ and $parts[$n] > $maxlat);
      $minlat = $parts[$n] if ($parts[$n] !~ /-/ and $parts[$n] < $minlat);
    }
  }
}

if ($fast){
  for(my $i=0;$i<$#hosts+1;$i++){
    for(my $j=0;$j<$i;$j++){
      $anet[$i][$j] = $anet[$j][$i];
    }
  }
}
    
if ($debug){
  foreach my $node (@hosts){
    print "For node $node:\n";
    foreach my $s (0..$#hosts){
      print "to $hosts[$s]: $anet[$hrank{$node}][$s][0], $anet[$hrank{$node}][$s][1]\n";
    }
    print "\n";
  }
}


# Get max_cpu
my $maxcpu = 0;
foreach my $cpuv (@acpu){
  $maxcpu = $cpuv if ($cpuv > $maxcpu);
}


# Pagrid output:
if($pagrid){
  print "% pagrid's processor grid:\n";
  
  # Get number of links
  my $nlink=0;
  foreach my $node (@aneig){
    $nlink += scalar @{$node};
  }
  $nlink /= 2;

  print scalar @hosts," $nlink\n";
  print "0 1 0.01\n";
  foreach my $n (0..$#hosts){
    my $cpuw = $maxcpu/$acpu[$n];
    printf "%.0f ", $cpuw;
    foreach my $nei (@{$aneig[$n]}){
      my $nnei = $hrank{$hname{$nei}};
      my $bdww = $maxbdw/$anet[$n][$nnei][0];
      my $latw = $maxbdw * $anet[$n][$nnei][1] * 1000 / $m;
      printf "%d %.0f %.0f ", $nnei, $bdww, $latw;
    }
    print "\n";
  }
  print "\n";
}

if ($weights){
  print "% mpi " if ($mpi);
  print "% cpu " if ($cpu);
  print "% udp " if ($udp);
  print "% tcp " if ($tcp);
#  print "% fast " if ($fast);
  print "\n";
  print "% weights: \n";
  
  foreach my $n (0..$#hosts){
    if ($cpu or $pagrid){
      printf "%s\: %.0f ",$hosts[$n], $maxcpu/$acpu[$n];
    }else{
      printf "%s\: 1 ",$hosts[$n];
    }
    foreach my $nei (@{$aneig[$n]}){
      my $nnei = $hrank{$hname{$nei}};
      my $bdw = $anet[$n][$nnei][0]/$minbdw;
      my $lat = $anet[$n][$nnei][1]/$minlat;
      printf "%s %.0f %.0f ", $hname{$nei}, $bdw, $lat;
    }
    print "\n";
  }
}
print "\n";

if ($values) {
  print "% mpi " if ($mpi);
  print "% cpu " if ($cpu);
  print "% udp " if ($udp);
  print "% tcp " if ($tcp);
#  print "% fast " if ($fast);
  print "\n";
  print "% values: \n";
  
  foreach my $n (0..$#hosts){
    if ($cpu or $pagrid){
      printf "%s\: %.2f ",$hosts[$n], $acpu[$n];
    }else{
      printf "%s\: 1 ",$hosts[$n];
    }
    foreach my $nei (@{$aneig[$n]}){
      my $nnei = $hrank{$hname{$nei}};
      my $bdw = $anet[$n][$nnei][0];
      my $lat = $anet[$n][$nnei][1];
      printf "%s %.2f %.2f ", $hname{$nei}, $bdw, $lat;
    }
    print "\n";
  }
}
